package utils

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"
)

// @author crastlin@163.com
// @date 2023-08-24

const HeaderJson = "json"
const HeaderXml = "xml"
const HeaderFormEncode = "formEncode"
const HeaderFormData = "formData"
const HeaderRaw = "raw"

type Request[T any] struct {
	host           string
	reqHeaderType  string
	opt            *RequestOption
	RespStatus     string
	RespStatusCode int
	ErrText        string
	ResCode        int
	Result         string
}

type RequestOption struct {
	params    map[string]any
	files     map[string]string
	headers   []map[string]string
	body      []byte
	timeout   time.Duration
	keepalive time.Duration
}

func ReqClient[T any](host string, headerType string, opt ...*RequestOption) *Request[T] {
	option := &RequestOption{timeout: 10}
	if len(opt) > 0 && opt[0] != nil {
		option = opt[0]
	}
	return &Request[T]{host: host, reqHeaderType: headerType, opt: option, ResCode: 500}
}

func (r *Request[T]) SetHeader(key string, value string) *Request[T] {
	r.opt.headers = append(r.opt.headers, map[string]string{key: value})
	return r
}

func (r *Request[T]) SetHeaders(headers []map[string]string) *Request[T] {
	r.opt.headers = headers
	return r
}

func (r *Request[T]) SetParameter(key string, value any) *Request[T] {
	r.opt.params[key] = value
	return r
}

func (r *Request[T]) SetParameters(params map[string]any) *Request[T] {
	r.opt.params = params
	return r
}

func (r *Request[T]) SetFile(name string, path string) *Request[T] {
	r.opt.files[name] = path
	return r
}

func (r *Request[T]) SetBody(body []byte) *Request[T] {
	r.opt.body = body
	return r
}

func (r *Request[T]) SetDial(timeout int, keepalive int) *Request[T] {
	r.opt.timeout = time.Duration(int64(timeout)) * time.Second
	r.opt.keepalive = time.Duration(int64(keepalive)) * time.Second
	return r
}

func (r *Request[T]) GetError() string {
	if r.RespStatusCode == 200 {
		return r.ErrText
	}
	return "请求网络错误"
}

func (r *Request[T]) GetResult() string {
	return r.Result
}

func (r *Request[T]) GetResultJson() T {
	var result T
	_ = json.Unmarshal([]byte(r.Result), &result)
	return result
}

func (r *Request[T]) setRequestHeaders(header *http.Header) {
	header.Set("Connection", "keep-alive")
	header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36")
	switch r.reqHeaderType {
	case HeaderJson:
		r.SetHeader("Content-Type", "application/json; charset=utf-8")
		break
	case HeaderXml:
		r.SetHeader("Content-Type", "application/xml; charset=utf-8")
		break
	case HeaderFormData:
		r.SetHeader("Content-Type", "multipart/form-data")
		break
	case HeaderFormEncode:
		r.SetHeader("Content-Type", "application/x-www-form-urlencoded")
		break
	}
	for _, headerSet := range r.opt.headers {
		for k, h := range headerSet {
			header.Set(k, h)
		}
	}
}

func (r *Request[T]) getRequestBody() io.Reader {
	var buf io.Reader
	switch r.reqHeaderType {
	case HeaderJson:
		body, _ := json.Marshal(r.opt.params)
		buf = bytes.NewBuffer(body)
		break
	case HeaderXml:
		buf = bytes.NewBuffer(r.opt.body)
		break
	case HeaderFormData:
		buf := &bytes.Buffer{}
		write := multipart.NewWriter(buf)
		if len(r.opt.files) > 0 {
			for field, path := range r.opt.files {
				part, err := write.CreateFormFile(field, path)
				if err != nil {
					panic("create multipart was failed: " + err.Error())
				}
				file, err := os.Open(path)
				if err != nil {
					panic("open upload file was failed: " + err.Error())
				}
				defer file.Close()
				_, err = io.Copy(part, file)
			}
		}
		if len(r.opt.params) > 0 {
			for field, value := range r.opt.params {
				_ = write.WriteField(field, fmt.Sprintf("%v", value))
			}
		}
		err := write.Close()
		if err != nil {
			panic("create multipart data was failed: " + err.Error())
		}
		break
	case HeaderRaw:
		req := http.Request{}
		_ = req
		if len(r.opt.params) > 0 {
			for field, value := range r.opt.params {
				req.Form.Add(field, fmt.Sprintf("%v", value))
			}
		}
		buf = bytes.NewReader([]byte(strings.TrimSpace(req.Form.Encode())))
		break
	case HeaderFormEncode:
		values := url.Values{}
		if len(r.opt.params) > 0 {
			for field, value := range r.opt.params {
				values.Add(field, fmt.Sprintf("%v", value))
			}
		}
		buf = strings.NewReader(values.Encode())
		break
	}
	return buf
}

func (r *Request[T]) Request(method string) *Request[T] {

	req, err := http.NewRequest(strings.ToUpper(method), r.host, r.getRequestBody())
	if err != nil {
		r.ErrText = err.Error()
		return r
	}
	r.setRequestHeaders(&req.Header)
	client := &http.Client{
		Timeout: r.opt.timeout,
		Transport: &http.Transport{
			DialContext: (&net.Dialer{
				Timeout:   r.opt.timeout,
				KeepAlive: r.opt.keepalive,
			}).DialContext,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
			MaxIdleConns:        100,
			IdleConnTimeout:     60 * time.Second,
			TLSHandshakeTimeout: 60 * time.Second,
		}}
	resp, err := client.Do(req)
	if err != nil {
		r.ErrText = err.Error()
		return r
	}
	defer resp.Body.Close()
	r.RespStatus = resp.Status
	r.RespStatusCode = resp.StatusCode
	fmt.Println("status", resp.Status)
	fmt.Println("response:", resp.Header)
	respBody, _ := ioutil.ReadAll(resp.Body)
	r.Result = string(respBody)
	fmt.Println("response:", r.Result)
	r.ErrText = ""
	r.ResCode = 200
	return r
}

func (r *Request[T]) Post() *Request[T] {
	return r.Request("post")
}

func (r *Request[T]) Get() *Request[T] {
	return r.Request("get")
}
